*... the space of n-note chords is an n-dimensional prism whose simplical ... faces are glued together with a twist, and whole remaining boundaries act like mirrors. Dmitri Tymoczko, A Geometry of Music: Harmony and Counterpoint in the Extended Common Practice, Oxford University Press, 2011, page 410.
This notebook is a demonstration of Orbichord, a project to explore topologically non-trivial space of music chords that are global-quotient orbifolds, see references:
Orbichord comes from combining the words orbifold and chord. It is a collection of python modules build on top of music21 project.
Import music and graphite modules
# Import music modules
import itertools
from music21.scale import ChromaticScale
from music21.interval import Interval
from music21.stream import Stream
from numpy import array, inf
from numpy import linalg as la
import networkx as nx
from orbichord.chordinate import standardSimplex, EfficientVoiceLeading
from orbichord.graph import createGraph, convertGraphToData
from orbichord.generator import Generator
import orbichord.identify as identify
from orbichord.utils import renderWithLily, playAudio
# Import graphic modules
import holoviews as hv
from holoviews import opts
hv.extension('plotly')
hv.output(size=180)
Create a dichord generator using a chromatic scale starting from C pitch. By default, chords are identified by normal ordered string, resulting in the space chord defined by a collection of pitch-class sets). I will be using also the normal ordered string to label the dichords to avoid any confusion because of the enharmonic equivalance.
scale = ChromaticScale('C')
chord_generator = Generator(
dimension = 3,
pitches = scale.getPitches('C','B'),
select = None
)
We can directly plot the space of trichords and show it is fundamentally a prism. For this, I compute standard simplex using the algorithm describe in figure B2, page 404 in Dmitri Tymoczko, A Geometry of Music. Moreover, I also applied affine transformation over the simplex coordinates defined as follow:
$$\begin{align} x' &= (x + y + z)/12 \\ y' &= (y - x)/12 \\ z' &= (z - y)/12 \end{align}$$# Generate data points and their labels
points = []
for chord in chord_generator.run():
points.append(standardSimplex(chord, scale))
# Plot the T^3/S_3 orbifold
hv.Scatter3D(points).opts(padding=0.05)
The resulting plot is similar to the one shown in page 86, figure 3.8.2 in Dmitri Tymoczko, A Geometry of Music: Harmony and Counterpoint in the Extended Common Practice, Oxford University Press, 2011.
We can simplify the ploting of the prism by slicing it horizontally, as it is done in page 89, figure 3.8.4 in Dmitri Tymoczko, A Geometry of Music: Harmony and Counterpoint in the Extended Common Practice, Oxford University Press, 2011.
For that we will change of ploting backend first.
hv.extension('bokeh')
hv.output(size=180)
# Generate data points and their labels
data = {}
names = {}
for chord in chord_generator.run():
x, y, z = standardSimplex(chord, scale)
data.setdefault(x, []).append([y, z])
names.setdefault(x, []).append(chord.normalOrderString)
# Define how to plot the slice of the prism
def draw_slice(index):
points = hv.Points(data[index])
labels = hv.Labels({('x', 'y'): data[index], 'labels': names[index]}, ['x', 'y'], 'labels')
overlay = (points * labels).redim.range(x=(-0.1, 1.1), y=(-0.1, 1.1))
return overlay.opts(
opts.Labels(text_font_size='10pt', yoffset=+.025),
opts.Points(color='blue', size=5, xlabel='y', ylabel='z'))
# Create a holomap with all the slides
slices = {slice: draw_slice(slice) for slice in sorted(names.keys())}
hv.HoloMap(slices, kdims='x')
The only missing points relative to Tymoczko's plot are those that are identified as the same chord. For example, the chord the chord with notes {F, F, D} with $x = 0$ is missing. This is because its normal ordered string is <25> that is also identify the chord {D, D, F} that can be found in slide with coordinate $x = 0.75$.